import OSS from "ali-oss";
import { getSystemConfigOss } from "@/api/system";
import { STORAGE_KEYS } from "@/constants/storage";

export enum OSS_DIR {
  /** 头像 */
  AVATAR = "avatar",
  /** 资源/头像 */
  RESOURCE_HEAD = "resource/head",
  /** 勋章 */
  BADGE = "badge",
  /** 广告 */
  BANNER = "banner",
  /** 文本框 */
  BUBBLE = "bubble",
  /** 财富等级图标 */
  CHARM_LEVEL = "charm_level",
  /** CP等级图标 */
  CP_LEVEL = "cp_level",
  /** 进场飘窗 */
  ENTRY_FLOAT = "entry_float",
  /** 游戏 */
  GAME = "game",
  /** 礼物 */
  GIFT = "gift",
  /** 头饰 */
  HEAD = "head",
  /** 充值等级 */
  RECHARGE_LEVEL = "recharge_level",
  /** 资料卡 */
  USER_CARD = "user_card",
  /** VIP等级标 */
  VIP = "vip",
  /** 声浪 */
  WAVE = "wave",
  /** 财富等级 */
  WEALTH_LEVEL = "wealth_level",
  /** 座驾 */
  VEHICLE = "vehicle",
}

class OSSClient {
  // 单例实例
  private static instance: OSSClient | null = null;
  // OSS客户端实例
  private client: OSS | null = null;
  // 过期时间
  private expireAt: number = 0;
  // 凭证过期缓冲时间(5分钟)
  private static readonly CREDENTIALS_EXPIRY_BUFFER = 5 * 60 * 1000;

  // 私有构造函数防止外部实例化
  private constructor() {}

  // 获取单例实例
  public static getInstance(): OSSClient {
    if (!OSSClient.instance) {
      OSSClient.instance = new OSSClient();
    }
    return OSSClient.instance;
  }

  // 获取OSS客户端，处理凭证过期逻辑
  public async getClient(): Promise<OSS> {
    // 检查客户端是否存在且凭证未过期
    if (
      this.client &&
      Date.now() < this.expireAt - OSSClient.CREDENTIALS_EXPIRY_BUFFER
    ) {
      return this.client;
    }

    // 从服务端获取临时凭证
    const { data } = await getSystemConfigOss();
    const {
      accessKeyId,
      accessKeySecret,
      securityToken,
      endpoint,
      bucketName,
      cdnDomain,
      expireAt,
    } = data;

    // 更新过期时间
    this.expireAt = expireAt;

    // 将cdnDomain保存到localStorage
    localStorage.setItem(STORAGE_KEYS.OSS_CDN_DOMAIN, cdnDomain);

    // 创建新的OSS客户端实例
    this.client = new OSS({
      endpoint,
      bucket: bucketName,
      accessKeyId,
      accessKeySecret,
      stsToken: securityToken,
      cdnDomain: cdnDomain,
      secure: true,
      // 连接超时时间(毫秒)
      connectionTimeout: 15 * 1000,
      // 套接字超时时间(毫秒)
      socketTimeout: 15 * 1000,
      // 最大并发请求数
      maxConcurrentRequest: 5,
      // 最大错误重试次数
      maxErrorRetry: 2,
      // 刷新间隔，设置为30分钟（比通常的1小时有效期短）
      refreshSTSTokenInterval: 30 * 60 * 1000,
    });

    return this.client;
  }


  // 在OSSClient类中添加检查文件是否存在的方法
  private async checkFileExists(filePath: string): Promise<boolean> {
    try {
      const client = await this.getClient();
      await client.getObjectMeta(filePath);
      // 报错说明文件存在
      return true; // 文件存在
    } catch (error) {
      console.error(error, "文件不存在");
      // 如果是404错误，表示文件不存在
      return false;
    }
  }

  // 上传文件
  public async uploadFile(
    file: File,
    fileName: string,
    dir: OSS_DIR
  ): Promise<string> {
    try {
      const cdnDomain = localStorage.getItem(STORAGE_KEYS.OSS_CDN_DOMAIN);
      const client = await this.getClient();

      const filePath = `${dir}/${fileName}`;

      // 检查文件是否存在
      // const fileExists = await this.checkFileExists(filePath);
      const fileExists = await this.checkFileExists(filePath);
      if (fileExists) {
        console.log(`文件 ${filePath} 已存在，直接返回链接`);
        return `${cdnDomain}/${filePath}`;
      }

      // 上传文件到OSS
      await client.put(filePath, file);
      return `${cdnDomain}/${filePath}`;
    } catch (error) {
      console.error("OSS上传失败:", error);
      // 提取更详细的错误信息
      const errorMessage = error instanceof Error ? error.message : "未知错误";

      throw new Error(`OSS上传失败: ${errorMessage}`);
    }
  }

  // 获取完整的OSS资源路径
  public getOssPath(directory: OSS_DIR, filename: string): string {
    // 这里假设OSS基础路径已配置在环境变量中
    const cdnDomain = localStorage.getItem(STORAGE_KEYS.OSS_CDN_DOMAIN);
    return `${cdnDomain}/${directory}/${filename}`;
  }
}

// 导出单例实例
export const ossClient = OSSClient.getInstance();

// 导出原函数接口，保持向后兼容性
export async function getOssClient() {
  return ossClient.getClient();
}

export async function uploadFile(file: File, fileName: string, dir: OSS_DIR) {
  return ossClient.uploadFile(file, fileName, dir);
}

export function getOssPath(directory: OSS_DIR, filename: string): string {
  return ossClient.getOssPath(directory, filename);
}
